package com.example.spring.framework.context;

import com.example.spring.framework.annotation.*;
import com.example.spring.framework.aop.AopProxy;
import com.example.spring.framework.aop.CglibAopProxy;
import com.example.spring.framework.aop.JdkDynamicAopProxy;
import com.example.spring.framework.aop.config.AopConfig;
import com.example.spring.framework.aop.support.AdvisedSupport;
import com.example.spring.framework.beans.BeanWrapper;
import com.example.spring.framework.beans.config.BeanDefinition;
import com.example.spring.framework.beans.config.BeanPostProcessor;
import com.example.spring.framework.beans.support.BeanDefinitionReader;
import com.example.spring.framework.beans.support.DefaultListableBeanFactory;
import com.example.spring.framework.core.BeanFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Classname ApplicationContext
 * @Date 2019/4/16 23:37
 * @Author admin
 * @Description Ioc核心容器
 */
public class ApplicationContext extends DefaultListableBeanFactory implements BeanFactory {

    private String[] configLocations;

    private BeanDefinitionReader reader;

    /**
     * 单例模式IoC容器缓存
     */
    private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();

    /**
     * 通用的IoC容器
     */
    private Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, BeanWrapper>();

    public ApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void refresh() throws Exception {
        //1、定位配置文件
        reader = new BeanDefinitionReader(this.configLocations);

        //2、加载配置文件，扫描相关的类，把它们封装成BeanDefinition
        List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

        //3、注册，把配置信息放到容器里面(伪IOC容器)
        doRegisterBeanDefinition(beanDefinitions);

        //4、把不是延时加载的类，有提前初始化
        doAutowrited();
    }

    private void doRegisterBeanDefinition(List<BeanDefinition> beanDefinitions) throws Exception {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
        //到这里为止，容器初始化完毕
    }

    /**
     * 只处理非延时加载的情况
     */
    private void doAutowrited() {
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 依赖注入，从这里开始，通过读取BeanDefinition中的信息
     * 然后，通过反射机制创建一个实例并返回
     * Spring做法是，不会把最原始的对象放出去，会用一个BeanWrapper来进行一次包装
     * 装饰器模式：
     * 1、保留原来的OOP关系
     * 2、我需要对它进行扩展，增强（为了以后AOP打基础）
     *
     * @param beanName
     * @return
     * @throws Exception
     */
    @Override
    public Object getBean(String beanName) throws Exception {
        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);

        Object instance = instantiateBean(beanName, beanDefinition);

        // 工厂模式 + 策略模式
        BeanPostProcessor postProcessor = new BeanPostProcessor();

        postProcessor.postProcessBeforeInitialization(instance, beanName);


        // 把对象封装到BeanWrapper中
        BeanWrapper beanWrapper = new BeanWrapper(instance);

        /**
         * 4、把BeanWrapper存到IOC容器里面
         * 1、初始化
         *  class A{ B b;}
         *  class B{ A a;}
         *  先有鸡还是先有蛋的问题，一个方法是搞不定的，要分两次
         * 2、拿到BeanWrapper之后，把BeanWrapper保存到IOC容器中去
         */
        this.factoryBeanInstanceCache.put(beanName, beanWrapper);
        if (beanName.indexOf("Mapper") != -1) {
            System.out.println(">>>>>> Mapper :" + beanName);
        }

        if (beanName.indexOf("Controller") != -1) {
            System.out.println(">>>>>> Controller :" + beanName);
        }

        if (beanName.indexOf("Service") != -1) {
            System.out.println(">>>>>> Service :" + beanName);
        }

        postProcessor.postProcessAfterInitialization(instance, beanName);

        //注入
        populateBean(beanName, new BeanDefinition(), beanWrapper);

        return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    }

    @Override
    public Object getBean(Class<?> beanClass) throws Exception {
        return getBean(beanClass.getName());
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig() {
        return reader.getConfig();
    }

    /**
     * 注入bean
     *
     * @param beanName
     * @param beanDefinition
     * @param beanWrapper
     */
    private void populateBean(String beanName, BeanDefinition beanDefinition, BeanWrapper beanWrapper) {
        Object instance = beanWrapper.getWrappedInstance();

        Class<?> clazz = beanWrapper.getWrappedClass();

        // 只有加了注解的类，才注入
        if (!(clazz.isAnnotationPresent(Controller.class)
                || clazz.isAnnotationPresent(Service.class)
                || clazz.isAnnotationPresent(Component.class)
                || clazz.isAnnotationPresent(Repository.class)
                || clazz.isAnnotationPresent(Mapper.class)
        )) {
            return;
        }
        if (clazz.getSimpleName().indexOf("Service") != -1) {
            System.out.println("this is a Service : " + clazz.getSimpleName());
        }

        // 获得所有的属性
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Autowired.class)) {
                continue;
            }
            Autowired autowired = field.getAnnotation(Autowired.class);

            String autowiredBeanName = autowired.value().trim();

            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);

            try {

                if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                    continue;
                }
                if (instance == null) {
                    continue;
                }


                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }

    private Object instantiateBean(String beanName, BeanDefinition beanDefinition) {

        //拿到要实例化的类名
        String className = beanDefinition.getBeanClassName();

        // 反射实例化得到一个对象
        Object instance = null;

        try {
            //假设默认就是单例,细节暂且不考虑，先把主线拉通
            if (this.factoryBeanObjectCache.containsKey(className)) {
                instance = this.factoryBeanObjectCache.get(className);
            } else {
                Class<?> clazz = Class.forName(className);

                instance = clazz.newInstance();

                AdvisedSupport advisedSupport = instantionAopConfig(beanDefinition);
                advisedSupport.setTargetClass(clazz);
                advisedSupport.setTargetObject(instance);

                //符合pointCut规则，创建代理对象
                if (advisedSupport.pointCutMatch()) {
                    instance = createProxy(advisedSupport).getProxy();
                }

                this.factoryBeanObjectCache.put(className, instance);
                this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(), instance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    private AdvisedSupport instantionAopConfig(BeanDefinition beanDefinition) {
        AopConfig aopConfig = new AopConfig();
        aopConfig.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        aopConfig.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        aopConfig.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        aopConfig.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        aopConfig.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        aopConfig.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new AdvisedSupport(aopConfig);

    }

    private AopProxy createProxy(AdvisedSupport advisedSupport) {
        Class targetClass = advisedSupport.getTargetClass();

        if (targetClass.getInterfaces().length > 0) {
            return new JdkDynamicAopProxy(advisedSupport);
        }
        return new CglibAopProxy(advisedSupport);
    }
}
